package com.gaogzhen.utils;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Stack;

/**
 * @author Administrator
 * @version 1.0
 * @description 简单计算器
 * @date 2022-10-31 20:26
 */
public class Calculator {
    /**
     * 表达式栈
     */
    private final Stack<String> stack = new Stack<>();//

    /**
     * 运用运算符ASCII码-40做索引的运算符优先级,%单独判断优先级和*，/同级
     */
    private final int[] OperatorPriorities = new int[]{4, 4, 2, 1, -1, 1, 3, 2};


    /**
     * 计算表达式的值
     *
     * @param expression 表达式
     * @return 计算结果
     */
    public  double execute(String expression, ExpressionType type) {
        if (type == null || ExpressionType.POSTFIX.equals(type)) {
            return calculatePostfix(expression);
        } else if (type.equals(ExpressionType.PREFIX)) {
            return calculatePrefix(expression);
        } else {
            throw new IllegalStateException("Unexpected type: " + type);
        }
    }

    /**
     * 计算表达式的值
     *
     * @param expression 表达式
     * @return 计算结果
     */
    public  double execute(String expression) {
        return calculatePostfix(expression);
    }

    /**
     * 计算后缀表达式的值
     *
     * @param expression 后缀表达式
     * @return 计算结果
     */
    private double calculatePostfix(String expression) {
        // 中缀转后缀
        convert(transform(expression), ExpressionType.POSTFIX);
        // 反正后缀栈
        Collections.reverse(stack);
        Stack<String> resultStack = new Stack<>();
        String firstValue, secondValue , currentOp;// 参与计算的第一个值，第二个值和算术运算符
        while (false == stack.isEmpty()) {
            currentOp = stack.pop();
            if (false == isOperator(currentOp.charAt(0))) {// 如果不是运算符则存入操作数栈中
                currentOp = currentOp.replace("~", "-");
                resultStack.push(currentOp);
            } else {// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算
                secondValue = resultStack.pop();
                firstValue = resultStack.pop();
                // 将负数标记符改为负号
                firstValue = firstValue.replace("~", "-");
                secondValue = secondValue.replace("~", "-");

                BigDecimal tempResult = calculate(firstValue, secondValue, currentOp.charAt(0));
                resultStack.push(tempResult.toString());
            }
        }
        return Double.parseDouble(resultStack.pop());
    }

    /**
     * 计算前缀表达式的值
     *
     * @param expression 前缀表达式
     * @return 计算结果
     */
    private double calculatePrefix(String expression) {
        // 中缀表达式转前缀表达式
        convert(transform(expression), ExpressionType.PREFIX);
        // 反转栈
        Collections.reverse(stack);
        Stack<String> resultStack = new Stack<>();
        String firstValue = null, secondValue = null, currentOp;// 参与计算的第一个值，第二个值和算术运算符
        while (false == stack.isEmpty()) {
            currentOp = stack.pop();
            if (false == isOperator(currentOp.charAt(0))) {// 如果不是运算符则存入操作数栈中
                currentOp = currentOp.replace("~", "-");
                resultStack.push(currentOp);
            } else {// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算
                firstValue = resultStack.pop();
                secondValue = resultStack.pop();
                // 将负数标记符改为负号
                firstValue = firstValue.replace("~", "-");
                secondValue = secondValue.replace("~", "-");

                BigDecimal tempResult = calculate(firstValue, secondValue, currentOp.charAt(0));
                resultStack.push(tempResult.toString());
            }
        }
        return Double.parseDouble(resultStack.pop());
    }

    /**
     * 数据准备阶段将表达式转换为指定类型的表达式
     *
     * @param expression 表达式
     * @param type       表达式类型
     */
    public String convert(String expression, ExpressionType type) {
        if (type == null || ExpressionType.POSTFIX.equals(type)) {
            return toPostfix(expression);
        } else if (type.equals(ExpressionType.PREFIX)) {
            return toPrefix(expression);
        } else {
            throw new IllegalStateException("Unexpected type: " + type);
        }

    }

    /**
     * 数据准备阶段将表达式转换成为后缀式栈
     *
     * @param expression 表达式
     */
    public String convert(String expression) {
        return toPostfix(expression);
    }

    /**
     * 中缀表达式转前缀表达式
     * @param expression    中缀表达式
     * @return              前缀表达式
     */
    private String toPrefix(String expression) {
        final Stack<Character> opStack = new Stack<>();
        opStack.push(',');// 运算符放入栈底元素逗号，此符号优先级最低
        char[] arr = expression.toCharArray();
        int len = arr.length;
        int currentIndex = len - 1 ;// 当前字符的位置
        int count = 0;// 上次算术运算符到本次算术运算符的字符的长度便于或者之间的数值
        char currentOp, peekOp;// 当前操作符和栈顶操作符
        // 自由向左扫描表达式
        for (int i = len - 1; i >= 0; i--,currentIndex--) {
            currentOp = arr[i];
            if (isOperator(currentOp)) {// 如果当前字符是运算符
                if (count > 0) {
                    stack.push(new String(arr, currentIndex + 1, count));// 取两个运算符之间的数字
                }
                peekOp = opStack.peek();
                if (currentOp == '(') {// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号
                    while (opStack.peek() != ')') {
                        stack.push(String.valueOf(opStack.pop()));
                    }
                    opStack.pop();
                } else {
                    while (currentOp != ')' && peekOp != ',' && peekOp !=')' &&comparePrefix(currentOp, peekOp)) {
                        stack.push(String.valueOf(opStack.pop()));
                        peekOp = opStack.peek();
                    }
                    opStack.push(currentOp);
                }
                count = 0;
            } else {
                count++;
            }
        }
        if (count > 1 || (count == 1 && !isOperator(arr[currentIndex + 1]))) {// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中
            stack.push(new String(arr, currentIndex + 1, count));
        }

        while (opStack.peek() != ',') {
            // 将操作符栈中的剩余的元素添加到后缀式栈中
            stack.push(String.valueOf(opStack.pop()));
        }
        StringBuilder sb = new StringBuilder();
        stack.forEach(sb::append);
        return sb.toString();
    }

    /**
     * 中缀表达式转后缀表达式
     * @param expression    中缀表达式
     * @return              后缀表达式
     */
    private String toPostfix(String expression) {
        final Stack<Character> opStack = new Stack<>();
        opStack.push(',');// 运算符放入栈底元素逗号，此符号优先级最低
        char[] arr = expression.toCharArray();
        int currentIndex = 0;// 当前字符的位置
        int count = 0;// 上次算术运算符到本次算术运算符的字符的长度便于或者之间的数值
        char currentOp, peekOp;// 当前操作符和栈顶操作符
        for (int i = 0; i < arr.length; i++) {
            currentOp = arr[i];
            if (isOperator(currentOp)) {// 如果当前字符是运算符
                if (count > 0) {
                    stack.push(new String(arr, currentIndex, count));// 取两个运算符之间的数字
                }
                peekOp = opStack.peek();
                if (currentOp == ')') {// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号
                    while (opStack.peek() != '(') {
                        stack.push(String.valueOf(opStack.pop()));
                    }
                    opStack.pop();
                } else {
                    while (currentOp != '(' && peekOp != ',' &&  peekOp != '(' &&comparePostfix(currentOp, peekOp)) {
                        stack.push(String.valueOf(opStack.pop()));
                        peekOp = opStack.peek();
                    }
                    opStack.push(currentOp);
                }
                count = 0;
                currentIndex = i + 1;
            } else {
                count++;
            }
        }
        if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中
            stack.push(new String(arr, currentIndex, count));
        }

        while (opStack.peek() != ',') {
            // 将操作符栈中的剩余的元素添加到后缀式栈中
            stack.push(String.valueOf(opStack.pop()));
        }
        StringBuilder sb = new StringBuilder();
        stack.forEach(sb::append);
        return sb.toString();
    }


    /**
     * 判断是否为算术符号
     *
     * @param c 字符
     * @return 是否为算术符号
     */
    private boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c == '%';
    }

    /**
     * 利用ASCII码-40做下标去算术符号优先级
     *
     * @param cur  下标
     * @param peek peek
     * @return 优先级，如果cur高或相等，返回true，否则false
     */
    private boolean comparePostfix(char cur, char peek) {// 如果是peek优先级高于cur，返回true，默认都是peek优先级要低
        final int offset = 40;
        if(cur  == '%'){
            // %优先级最高
            cur = 47;
        }
        if(peek  == '%'){
            // %优先级最高
            peek = 47;
        }

        return OperatorPriorities[peek - offset] >= OperatorPriorities[cur - offset];
    }

    private boolean comparePrefix(char cur, char peek) {// 如果是peek优先级高于cur，返回true，默认都是peek优先级要低
        final int offset = 40;
        if(cur  == '%'){
            // %优先级最高
            cur = 47;
        }
        if(peek  == '%'){
            // %优先级最高
            peek = 47;
        }

        return OperatorPriorities[peek - offset] > OperatorPriorities[cur - offset];
    }

    /**
     * 按照给定的算术运算符做计算
     *
     * @param firstValue  第一个值
     * @param secondValue 第二个值
     * @param currentOp   算数符，只支持'+'、'-'、'*'、'/'、'%'
     * @return 结果
     */
    private BigDecimal calculate(String firstValue, String secondValue, char currentOp) {
        BigDecimal result;
        switch (currentOp) {
            case '+':
                result = NumberUtil.add(firstValue, secondValue);
                break;
            case '-':
                result = NumberUtil.sub(firstValue, secondValue);
                break;
            case '*':
                result = NumberUtil.mul(firstValue, secondValue);
                break;
            case '/':
                result = NumberUtil.div(firstValue, secondValue);
                break;
            case '%':
                result = NumberUtil.toBigDecimal(firstValue).remainder(NumberUtil.toBigDecimal(secondValue));
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + currentOp);
        }
        return result;
    }

    /**
     * 将表达式中负数的符号更改
     *
     * @param expression 例如-2+-1*(-3E-2)-(-1) 被转为 ~2+~1*(~3E~2)-(~1)
     * @return 转换后的字符串
     */
    private static String transform(String expression) {
        expression = StrUtil.cleanBlank(expression);
        expression = StrUtil.removeSuffix(expression, "=");
        final char[] arr = expression.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == '-') {
                if (i == 0) {
                    arr[i] = '~';
                } else {
                    char c = arr[i - 1];
                    if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == 'E' || c == 'e') {
                        arr[i] = '~';
                    }
                }
            }
        }
        if (arr[0] == '~' && (arr.length > 1 && arr[1] == '(')) {
            arr[0] = '-';
            return "0" + new String(arr);
        } else {
            return new String(arr);
        }
    }
}
